/**
 * 
 */
package org.mhz.sit.controller;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Paint;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.mhz.sit.entity.Settings;
import org.mhz.sit.entity.Speaker;
import org.mhz.sit.entity.SpeakerOffTopicOrder;
import org.mhz.sit.entity.SpeakerOrder;
import org.mhz.sit.service.ConfigureSettingsService;
import org.mhz.sit.service.ConfigureSpeakersService;
import org.mhz.sit.service.SettingsService;
import org.mhz.sit.service.SpeakerService;
import org.mhz.sit.util.AlertUtil;
import org.mhz.sit.util.Constants;
import org.mhz.sit.util.SettingsUtil;
import org.mhz.sit.util.SpeakerUtil;
import org.mhz.sit.util.TimeUtil;
import org.mhz.sit.util.ValidationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Mahan
 *
 * Speak in time controller
 */
public class SpeakInTimeController implements Initializable 
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigAndSettingsController.class);
       
    @FXML private Label timeLabel = new Label();
    @FXML private Label currentSpeakerVal = new Label();
    @FXML private static Label topicVal = new Label();
            
    @FXML private Button nextSpeakerBtn = new Button();
    @FXML private Button startBtn = new Button();
    @FXML private Button stopBtn = new Button();
    @FXML private Button resetBtn = new Button();
    
    @FXML private Button moveToOffTopicButton = new Button();
    @FXML private Button moveToSpeakerOrderButton = new Button();
    
    @FXML private TextField speakerNumOrNameField = new TextField();
    @FXML private ListView<SpeakerOrder> speakerOrderList = new ListView<SpeakerOrder>();
    @FXML private ListView<SpeakerOffTopicOrder> offTopicOrderList = new ListView<SpeakerOffTopicOrder>();
    
    private String language = null;
    private String timeLimit = null;
    private String topic = null;
        
    private boolean isNewSpeaker = false;
    
    private SpeakerService mConfigureSpeakersService = new ConfigureSpeakersService();
    private SettingsService mConfigureSettingsService = new ConfigureSettingsService();
        
    private Timeline digitalTime = null;
    
    /**
     * Initialise the list
     */
    private void initialiseSpeakerOrderList()
    {
        // Enale buttons if there is a current speaker
        if (SpeakerUtil.getCurrentSpeaker() != null)
        {
            // Enable button when there is a current speaker
            startBtn.setDisable(false);
            stopBtn.setDisable(false);
            resetBtn.setDisable(false);                      
        }
        
        // If there are speakers in the order list
        if (CollectionUtils.isNotEmpty(SpeakerUtil.getObservableOrderOfSpeakersList()))
        {
            nextSpeakerBtn.setDisable(false);
            moveToOffTopicButton.setDisable(false);                                      
        }
        else
        {
            nextSpeakerBtn.setDisable(true);
            moveToOffTopicButton.setDisable(true);
        }        
    }
    
    /**
     * Initialise the list
     */
    private void initialiseSpeakerOffTopicList()
    {
        // If there are speakers in the order list
        if (CollectionUtils.isNotEmpty(SpeakerUtil.getObservableOffTopicOrderList()))
        {
            moveToSpeakerOrderButton.setDisable(false);                                       
        }
        else
        {
            moveToSpeakerOrderButton.setDisable(true);                 
        }        
    }
    
    /**
     * Initialise settings
     */
    private void initialiseSettings()
    {
        if (CollectionUtils.isNotEmpty(SettingsUtil.getSettingsList()))
        {
            for (Settings settings : SettingsUtil.getSettingsList())
            {
                if (settings.getType().equalsIgnoreCase(Constants.LANGUAGE))
                {
                    language = settings.getValue();
                }
                else if (settings.getType().equalsIgnoreCase(Constants.TIME_LIMIT))
                {
                    timeLimit = settings.getValue();
                }
                else if (settings.getType().equalsIgnoreCase(Constants.TOPIC))
                {
                    topic = settings.getValue();
                    topicVal.setText(topic);
                }                
            }            
        }       
    }    
    /* (non-Javadoc)
     * @see javafx.fxml.Initializable#initialize(java.net.URL, java.util.ResourceBundle)
     */
    @Override
    public void initialize(URL aArg0, ResourceBundle aArg1)
    {
        // Get the speakers
        final List<Speaker> speakers = mConfigureSpeakersService.getList();        
        SpeakerUtil.setObservableSpeakerList(FXCollections.observableList(speakers));
        
        // Get the order of speakers
        final List<SpeakerOrder> orderOfSpeakers = mConfigureSpeakersService.getOrderOfSpeakers();
        SpeakerUtil.setObservableOrderOfSpeakersList(FXCollections.observableList(orderOfSpeakers));
        speakerOrderList.setItems(SpeakerUtil.getObservableOrderOfSpeakersList());
        initialiseSpeakerOrderList();
        
        // Get the off topic order
        final List<SpeakerOffTopicOrder> offTopicOrder = mConfigureSpeakersService.getOffTopicOrder();
        SpeakerUtil.setObservableOffTopicOrderList(FXCollections.observableList(offTopicOrder));
        offTopicOrderList.setItems(SpeakerUtil.getObservableOffTopicOrderList());
        initialiseSpeakerOffTopicList();
        
        // Get the settings
        SettingsUtil.setSettingsList(FXCollections.observableList(mConfigureSettingsService.getList()));
        initialiseSettings();
        
        // Listen for changes to the order of speakers list
        SpeakerUtil.getObservableOrderOfSpeakersList().addListener(new ListChangeListener<Object>() 
        {
            @Override
            public void onChanged(final ListChangeListener.Change change) 
            {
                initialiseSpeakerOrderList();
            }
        });
        
        // Listen for changes to the order of off topic list
        SpeakerUtil.getObservableOffTopicOrderList().addListener(new ListChangeListener<Object>() 
        {
            @Override
            public void onChanged(final ListChangeListener.Change change) 
            {
                initialiseSpeakerOffTopicList();
            }
        });
        
        // If there are speakers in the order list
        if (CollectionUtils.isNotEmpty(SpeakerUtil.getObservableOrderOfSpeakersList()))
        {
            SpeakerUtil.setCurrentSpeaker(SpeakerUtil.getObservableOrderOfSpeakersList().get(0));
            
            // Place the first speaker in the current speaker section
            // and remove it from the list
            currentSpeakerVal.setText(SpeakerUtil.getCurrentSpeaker().getName());
            SpeakerUtil.getObservableOrderOfSpeakersList().remove(SpeakerUtil.getCurrentSpeaker());                        
        }
        
        // the digital clock updates once a second.
        digitalTime = new Timeline(new KeyFrame(
                Duration.seconds(0), new EventHandler<ActionEvent>()
                {
                    @Override
                    public void handle(ActionEvent actionEvent)
                    {
                        if (isNewSpeaker)
                        {
                            // Don't start the timer immediately when
                            // it is a new speaker.
                            // This is so the time gets set to 00:00
                            // before it starts counting up.
                        }
                        else
                        {
                            final String[] timeSplit = timeLabel.getText().split(Constants.TIME_SEPARATOR);
                            
                            int minutes = Integer.valueOf(timeSplit[0]).intValue();
                            int seconds = Integer.valueOf(timeSplit[1]).intValue();
                            
                            if (seconds >= Constants.END_OF_MINUTE)
                            {
                                seconds = Constants.ZERO;
                                minutes++;
                                
                                // If a time limit was specified
                                if (StringUtils.isNotBlank(timeLimit) && (Integer.valueOf(timeLimit) > 1))
                                {
                                    final int timeLimitValue = Integer.valueOf(timeLimit);
                                    
                                    // Warning color 1 minute before time limit is reached
                                    if ((minutes >= (timeLimitValue - 1)) && (minutes < timeLimitValue))
                                    {
                                        ValidationUtil.setTimeStyleToLabel(timeLabel, 
                                                                           Constants.WARNING);
                                    }
                                    else if (minutes >= timeLimitValue)
                                    {
                                        ValidationUtil.setTimeStyleToLabel(timeLabel, 
                                                                           Constants.OVER_TIME);                                    
                                    }
                                    else
                                    {
                                        ValidationUtil.setTimeStyleToLabel(timeLabel, 
                                                                           Constants.NORMAL_TIME);                                    
                                    }
                                }                            
                            }
                            else
                            {
                                seconds++;                           
                            }
                            
                            if (minutes >= Constants.END_OF_MINUTE)
                            {
                                minutes = Constants.ZERO;
                            }                        
                            
                            final String minutesAsString = TimeUtil.pad(Constants.TIME_UNIT_LENGHT, 
                                                                        Constants.ZERO_PADDING, 
                                                                        String.valueOf(minutes));
                            final String secondAsString = TimeUtil.pad(Constants.TIME_UNIT_LENGHT, 
                                                                       Constants.ZERO_PADDING, 
                                                                       String.valueOf(seconds));
                                                    
                            timeLabel.setText(minutesAsString + Constants.TIME_SEPARATOR + secondAsString); 
                        }
                        
                        isNewSpeaker = false;
                    }
                }), new KeyFrame(Duration.seconds(1)));
        
        // time never ends.
        digitalTime.setCycleCount(Animation.INDEFINITE);        
    }     
    
    /**
     * Handle configuration and settings button click
     *
     * @param event - The event
     * @throws IOException
     */
    @FXML protected void handleConfigButtonClick(ActionEvent event) throws IOException
    {       
        final URL url = getClass().getClassLoader().getResource(Constants.CONFIG_SETTINGS_FXML);
        final Parent root = FXMLLoader.load(url);
        
        final Stage configStage = new Stage();
        configStage.initModality(Modality.APPLICATION_MODAL);
        configStage.setTitle(Constants.CONFIG_SETTINGS_TITLE);
        final Scene scene = new Scene(root, Paint.valueOf(Constants.BACKGROUND));
        scene.getStylesheets().add(Constants.MAIN_STYLESHEET);
        
        configStage.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST,
                new EventHandler<WindowEvent>()
                {
                    @Override
                    public void handle(final WindowEvent window)
                    {
                        initialiseSettings();
                        
                        if (SpeakerUtil.getCurrentSpeaker() == null)
                        {
                            currentSpeakerVal.setText("No one yet!");
                            
                            // Disable button when there is a current speaker
                            startBtn.setDisable(true);
                            stopBtn.setDisable(true);
                            resetBtn.setDisable(true);
                            
                            if (digitalTime != null)
                            {
                                digitalTime.stop();
                            }
                            timeLabel.setText(Constants.START_TIME);
                        }
                    }
                });
        
        configStage.setScene(scene);
        configStage.show();              
    }   
    
    /**
     * Handle next speaker button click
     *
     * @param event - The event
     * @throws IOException
     */
    @FXML protected void handleNextSpeakerButtonClick(ActionEvent event) throws IOException
    {     
        isNewSpeaker = true;
        
        // Stop the clock
        digitalTime.stop();
        
        // Every new speaker starts at zero
        ValidationUtil.setTimeStyleToLabel(timeLabel, Constants.NORMAL_TIME);        
        timeLabel.setText(Constants.START_TIME);           
        
        if (CollectionUtils.isNotEmpty(SpeakerUtil.getObservableOrderOfSpeakersList()))
        {
            SpeakerOrder nextSpeaker = null;
            
            // If there are still speakers in the order
            if (CollectionUtils.isNotEmpty(SpeakerUtil.getObservableOrderOfSpeakersList()))
            {
                // Get the next speaker
                nextSpeaker = SpeakerUtil.getObservableOrderOfSpeakersList().get(0);               
            }
            
            // Remove the current speaker from the order of speakers
            if (SpeakerUtil.getCurrentSpeaker() != null)
            {                
                mConfigureSpeakersService.deleteSpeakerOrder(SpeakerUtil.getCurrentSpeaker(), 
                                                             SpeakerUtil.getObservableOrderOfSpeakersList()); 
            }
            
            // If there is a next speaker
            if (nextSpeaker != null)
            {
                SpeakerUtil.setCurrentSpeaker(nextSpeaker);
                currentSpeakerVal.setText(SpeakerUtil.getCurrentSpeaker().getName());     
                SpeakerUtil.getObservableOrderOfSpeakersList().remove(SpeakerUtil.getCurrentSpeaker());                 
            }
            
        }

        /*try
        {
            Thread.sleep(Constants.ONE_SEC_IN_MS);
        }
        catch (InterruptedException intEx)
        {
            LOGGER.warn("Got interrupted while trying to sleep");
        }*/
        
        // start the clock.
        digitalTime.play();        
    }
    
    /**
     * Handle stop time button click
     *
     * @param event - The event
     * @throws IOException
     */
    @FXML protected void handleStopTimeButtonClick(ActionEvent event) throws IOException
    {
        digitalTime.stop();
    }
    
    /**
     * Handle start time button click
     *
     * @param event - The event
     * @throws IOException
     */
    @FXML protected void handleStartTimeButtonClick(ActionEvent event) throws IOException
    {
        digitalTime.play();
    }
    
    /**
     * Handle reset time button click
     *
     * @param event - The event
     * @throws IOException
     */
    @FXML protected void handleResetTimeButtonClick(ActionEvent event) throws IOException
    {
        digitalTime.stop();
        
        ValidationUtil.setTimeStyleToLabel(timeLabel, Constants.NORMAL_TIME);  
        
        // Every new speaker start at zero
        timeLabel.setText(Constants.START_TIME);
    }
    
    /**
     * Add the speaker to the order of speakers
     */
    private void addSpeakerToOrder()
    {
        final List<Speaker> matchingSpeakers = new ArrayList<Speaker>();
        final String speakerToFind = speakerNumOrNameField.getText();
        
        // If a value has been entered
        if (StringUtils.isNotBlank(speakerToFind))
        {
            // If there are speakers to find
            if (CollectionUtils.isNotEmpty(SpeakerUtil.getObservableSpeakerList()))
            {
                for (Speaker speaker : SpeakerUtil.getObservableSpeakerList())
                {
                    final String id = String.valueOf(speaker.getId());
                    final String name = speaker.getName();
                    
                    // If it is a number
                    if (ValidationUtil.isNumber(speakerToFind))
                    {
                        if (speakerToFind.equals(id))
                        {
                            matchingSpeakers.add(speaker);
                            break;
                        }
                    }
                    // If it is a name
                    else
                    {
                        // Split the name up
                        final String[] splitName = name.split(" ");
                        
                        // For each part of the name check if it matches
                        for (int i = 0; i < splitName.length; i++)
                        {
                            if (splitName[i].toLowerCase().startsWith(speakerToFind.toLowerCase()))
                            {                            
                                matchingSpeakers.add(speaker);
                            }
                        }
                    }
                }
                
                // If matching speakers were found
                if (CollectionUtils.isNotEmpty(matchingSpeakers))
                {
                    final int numOfSpeakersFound = matchingSpeakers.size();
                    
                    // If only one matching speaker was found
                    if (numOfSpeakersFound == 1)
                    {
                        final SpeakerOrder speakerOrder = new SpeakerOrder(matchingSpeakers.get(0).getId(),
                                                                           matchingSpeakers.get(0).getName());
                        
                        mConfigureSpeakersService.addToSpeakerOrder(speakerOrder, 
                                                                    SpeakerUtil.getObservableOrderOfSpeakersList());
                    }
                    // If more than one matching speaker was found
                    else if (numOfSpeakersFound > 1)
                    {
                        final StringBuilder speakerNames = new StringBuilder();
                        String separator = "";
                        for (Speaker speaker : matchingSpeakers)
                        {
                            speakerNames.append(separator);
                            speakerNames.append(speaker.getName());                        
                            separator = "\n";
                        }
                        
                        AlertUtil.displayAlert(Constants.SIT_004_MORE_THAN_ONE_MATCH + speakerNames); 
                    }
                }
                // No matching speakers were found
                else
                {
                    AlertUtil.displayAlert(Constants.SIT_005_COULD_NOT_FIND);
                }    
            }
            else
            {
                AlertUtil.displayAlert(Constants.SIT_006_NO_CONFIGURED_SPEAKERS);
            }
        }
        else
        {
            AlertUtil.displayAlert(Constants.SIT_007_FIELD_MUST_NOT_BE_BLANK);
        }        
    }
    
    /**
     * Handle add to speaker order button click
     *
     * @param event - The event
     * @throws IOException
     */    
    @FXML protected void handleAddToSpeakerOrderButtonClick(ActionEvent event) throws IOException
    {
        addSpeakerToOrder();
    }
    
    @FXML protected void handleAddSpeakerOrderKeyPress(KeyEvent event)
    {
        if (event != null)
        {
            if (event.getCode() == KeyCode.ENTER)
            {
                addSpeakerToOrder();
            }
        }
    }
    
    /**
     * Handle move to off topic order button click
     *
     * @param event - The event
     * @throws IOException
     */    
    @FXML protected void handleMoveToOffTopicButtonClick(ActionEvent event) throws IOException
    {
        final SpeakerOrder selectedSpeaker = speakerOrderList.getSelectionModel().getSelectedItem();
             
        if (selectedSpeaker == null)
        {
            AlertUtil.displayAlert(Constants.SIT_008_MUST_SELECT_AN_ITEM);
        }
        
        mConfigureSpeakersService.moveSpeaker(selectedSpeaker, 
                                              SpeakerUtil.getObservableOrderOfSpeakersList(), 
                                              SpeakerUtil.getObservableOffTopicOrderList());
        
        speakerOrderList.getSelectionModel().clearSelection();
    }  
    
    /**
     * Handle move to speaker order button click
     *
     * @param event - The event
     * @throws IOException
     */    
    @FXML protected void handleMoveToSpeakerButtonClick(ActionEvent event) throws IOException
    {
        final SpeakerOffTopicOrder selectedSpeaker = offTopicOrderList.getSelectionModel().getSelectedItem();
                
        if (selectedSpeaker == null)
        {
            AlertUtil.displayAlert(Constants.SIT_008_MUST_SELECT_AN_ITEM);
        }
        
        mConfigureSpeakersService.moveSpeaker(selectedSpeaker, 
                                              SpeakerUtil.getObservableOrderOfSpeakersList(), 
                                              SpeakerUtil.getObservableOffTopicOrderList());   
        
        offTopicOrderList.getSelectionModel().clearSelection();
    }   
}
